using System.Collections.Generic;
using System.Diagnostics;

namespace rsync
{
    /// <summary>
    /// Session负责管理想要一起玩游戏的设备池。
///每个设备必须在自己的端创建一个Session，并确保所有设备id，每个设备的玩家数量，在每个设备上都是相同的。
    /// <br />
    /// <listheader>Terminology</listheader>
    /// <list type="bullet">
    ///     <item>Frame: 游戏逻辑的一次更新，为了正确模拟游戏的一帧，你将需要该帧的所有玩家输入。</item>
    ///     <item>Device: 运行游戏的设备(如电脑、xbox等)，一个或多个玩家可以在其中玩游戏。</item>
    ///     <item>Player: 设备上的人(或AI)产生改变游戏状态的输入.</item>
    ///     <item>DeviceID: 一个唯一的数字，用来唯一地标识一个设备，从0开始(例如。0 =约翰的Xbox, 1 =汤姆的Switch)</item>
    ///     <item>Rollback: 当Session等待远程设备输入时，它可能会使用占位符输入，以便你的游戏能够继续，但如果远程玩家的输入不同，我们就需要用正确的输入重新模拟游戏:这就是回滚</item>
    ///     <item>PlayerInput: 描述玩家想要如何改变给定帧的游戏状态的信息。(如。左=假，右=真，跳跃=真)</item>
    ///     <item>PlayerCount: 特定设备上的玩家数量，通常对于大多数游戏来说是一个，但对于拥有多个本地玩家的游戏来说可能更多。splitscreen).</item>
    /// </list>
    /// </summary>
    public abstract class Session
    {
        public const uint LIMIT_INPUT_SIZE = 32;
        public const uint LIMIT_DEVICE_COUNT = 4;
        public const uint LIMIT_TOTAL_PLAYER_COUNT = 16;

        /// <summary>
        /// InputSize是一个播放器输入的比特大小。
        /// </summary>
        protected uint InputSize;
        /// <summary>
        /// DeviceCount参加此会话的设备数量是多少.
        /// </summary>
        protected readonly uint DeviceCount;
        /// <summary>
/// TotalPlayerCount是所有设备上参与此会话的玩家总数。
/// </summary>
        protected readonly uint TotalPlayerCount;

        /// <summary>
        /// LocalDevice表示本会话的本地设备。
        /// </summary>
        internal protected abstract Device LocalDevice { get; }
        /// <summary>
        /// AllDevices是参与此会话的每个设备(本地和远程)的数组，由它们的设备ID索引。
        /// </summary>
        internal protected abstract Device[] AllDevices { get; }

        /// <param name="inputSize">一个播放器输入的字节大小.</param>
        /// <param name="deviceCount">参与此会话的设备数量.</param>
        /// <param name="totalPlayerCount">所有设备上参与此会话的玩家总数.</param>
        public Session(uint inputSize, uint deviceCount, uint totalPlayerCount)
        {
            Debug.Assert(inputSize > 0);
            Debug.Assert(inputSize <= LIMIT_INPUT_SIZE);
            Debug.Assert(deviceCount >= 2);
            Debug.Assert(deviceCount <= LIMIT_DEVICE_COUNT);
            Debug.Assert(totalPlayerCount >= 2);
            Debug.Assert(totalPlayerCount <= LIMIT_TOTAL_PLAYER_COUNT);

            InputSize = inputSize;
            DeviceCount = deviceCount;
            TotalPlayerCount = totalPlayerCount;
        }
        /// <summary>
        /// SetLocalDevice告诉给定的设备是本会话的本地设备，并承载给定的玩家计数。
///在调用AddRemoteDevice之前必须调用SetLocalDevice
        /// </summary>
        /// <param name="deviceId">用于标识本地设备的唯一编号。此数字在该特定设备的每个会话中必须完全相同</param>
        /// <param name="playerCount">在此设备上玩游戏的玩家数量。此数字在该特定设备的每个会话中必须完全相同</param>
        /// <param name="frameDelay">在注册本地输入之前要跳过的帧数，用于避免每帧都回滚.</param>
        public abstract void SetLocalDevice(uint deviceId, uint playerCount, uint frameDelay);
        /// <summary>
        /// AddRemoteDevice告诉给定的设备是远程到这个会话，并承载给定的玩家计数。
///在调用AddRemoteDevice之前必须调用SetLocalDevice
        /// </summary>
        /// <param name="deviceId">用于标识本地设备的唯一编号。此数字在该特定设备的每个会话中必须完全相同</param>
        /// <param name="playerCount">在此设备上玩游戏的玩家数量。此数字在该特定设备的每个会话中必须完全相同</param>
        /// <param name="remoteConfiguration">由于给定的设备不是会话的本地设备，我们必须提供一种与给定设备通信的方法，此配置将传递给会话适配器</param>
        public abstract void AddRemoteDevice(uint deviceId, uint playerCount, object remoteConfiguration);

        /// <summary>
        /// 必须定期调用轮询，以使会话有机会执行一些工作并同步设备.
        /// </summary>
        public abstract void Poll();
        /// <summary>
        /// 当所有的会话都同步并准备好接受输入时，IsRunning返回true。
        /// </summary>
        public abstract bool IsRunning();
        /// <summary>
        /// AdvanceFrame将告诉会话增加当前帧，并且你已经准备好处理下一帧。
        /// </summary>
        /// <param name="localInput">当前帧的本地设备输入</param>
        /// <returns>在再次调用AdvanceFrame之前按顺序执行的操作列表</returns>
        public abstract List<SessionAction> AdvanceFrame(byte[] localInput);
        internal protected abstract uint SendMessageTo(uint deviceId, DeviceMessage message);
        internal protected abstract void AddRemoteInput(uint deviceId, DeviceInputMessage message);
    }
}